﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace IndentationSplitter
{
    /// <summary>Static accessor to help functions</summary>
    public class SimpleTree
    {
        /// <summary>Parse the given text to output models for each indentation level</summary>
        /// <typeparam name="TModelType">The model to create for each row</typeparam>
        /// <param name="indentedText">A set of indented lines, either with spaces or tabs</param>
        /// <param name="factoryHandler">A factory method to use for creating each model in the tree</param>
        /// <returns>A list of root level tree nodes</returns>
        public static IEnumerable<TModelType> Parse<TModelType>(string indentedText, TreeItemFactoryHandler<TModelType> factoryHandler)
        {
            var rootItems = new IndentedTree(indentedText).RootItems;

            foreach (var item in rootItems)
                yield return BuildItem(item, factoryHandler);
        }

        private static TModelType BuildItem<TModelType>(TreeNode item, TreeItemFactoryHandler<TModelType> factoryHandler)
        {
            // Build children to be able to put these into the model factory
            var children = item.Children.Select(c => BuildItem(c, factoryHandler)).ToArray();

            return factoryHandler(item.Value, children);
        }

        /// <summary>Formats the tree items into an indented line by line text</summary>
        /// <typeparam name="TModelType">The type of model to format</typeparam>
        /// <param name="items">An enumeration of the root items</param>
        /// <param name="modelFormatter">A formatter to provide a text per tree item</param>
        /// <param name="modelChildrenHandler">A handler to provide the children for a tree item</param>
        /// <param name="indentation">The type of indentation to use, defaults to tabs</param>
        /// <returns>The items formatted as an intended tree</returns>
        public static string Format<TModelType>(IEnumerable<TModelType> items, ModelFormatterHandler<TModelType> modelFormatter, ModelChildrenHandler<TModelType> modelChildrenHandler, string indentation = "\t")
        {
            var formatted = items.Select(item => FormatElement(item, modelFormatter, modelChildrenHandler, indentation));
            return string.Join(Environment.NewLine, formatted);
        }

        private static string FormatElement<TModelType>(TModelType item, ModelFormatterHandler<TModelType> modelFormatter, ModelChildrenHandler<TModelType> modelChildrenHandler, string indentationString, string currentIndentation = "")
        {
            string output = currentIndentation + modelFormatter(item);
            var childEnumeration = modelChildrenHandler(item);
            if (childEnumeration == null)
                return output;

            var children = childEnumeration.ToList();
            if (children.Any() == false)
                return output;

            currentIndentation += indentationString;
            var childrenFormatted =
                children.Select(child => FormatElement(child, modelFormatter, modelChildrenHandler, indentationString, currentIndentation));

            return output + Environment.NewLine + string.Join(Environment.NewLine, childrenFormatted);
        }
    }

    /// <summary>Factory method for creating a tree node item</summary>
    /// <typeparam name="TModelType">The type of model to create</typeparam>
    /// <param name="treeItemText">The text of the indented line</param>
    /// <param name="children">Children to assign to the item</param>
    /// <returns>Should return a created model</returns>
    public delegate TModelType TreeItemFactoryHandler<TModelType>(string treeItemText, IEnumerable<TModelType> children);

    /// <summary>Formatter for a given model type</summary>
    /// <typeparam name="TModelType">Type of model to provide a formatting for</typeparam>
    /// <param name="model">The model to format</param>
    /// <returns>Should generate a descriptive string for the model to be output into the tree after indentation</returns>
    public delegate string ModelFormatterHandler<in TModelType>(TModelType model);

    /// <summary>Handler to retrieve the children of a given model in a tree</summary>
    /// <typeparam name="TModelType">The model type to retrieve children for</typeparam>
    /// <param name="model">The model instance to retrieve children for</param>
    /// <returns>An enumerable (or empty list) for the models children</returns>
    public delegate IEnumerable<TModelType> ModelChildrenHandler<TModelType>(TModelType model);
}